package com.fmy.mcsecurity.token.impl;

import cn.hutool.core.collection.CollUtil;
import com.alicp.jetcache.Cache;
import com.fmy.mcsecurity.commons.tools.SecurityTool;
import com.fmy.mcsecurity.token.TokenStorage;
import com.fmy.mcsecurity.token.model.RefreshStorage;
import com.fmy.mcsecurity.token.model.SecurityToken;
import org.springframework.security.core.Authentication;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @author Mr.fmy
 * @version 1.0.0
 * @ClassName JetcacheTokenStorage
 * @Description TODO Jetcache实现方式
 * @createTime 2020/6/17 08:58
 */
public class JetcacheTokenStorage implements TokenStorage {

    /**
     * jetCache
     */
    private final Cache multiLevelCache;

    /**
     * 默认同时在线一个
     */
    private int concurrency = 1;

    /**
     * k: token
     * v: user_key
     */
    private final String JETCACHE_TOKEN = "JETCACHE_TOKEN";

    /**
     * k: REFRESH_TOKEN
     * v: {@link RefreshStorage}
     */
    private final String JETCACHE_REFRESH_TOKEN = "JETCACHE_REFRESH_TOKEN";

    /**
     * 统一用户同一端口 可通用一个 AUTHENTICATION
     * k：user_key
     * v: Authentication
     */
    private final String JETCACHE_AUTHENTICATION = "JETCACHE_AUTHENTICATION";

    /**
     * k：user_key
     * v：对应的token集合 {@link SecurityToken}
     */
    private final String JETCACHE_USER_LIST = "JETCACHE_USER_LIST";

    /**
     * k: user_key
     * v: 剔出信息
     */
    private final String JETCACHE_KIKT = "JETCACHE_KIKT";

    public JetcacheTokenStorage(Cache multiLevelCache) {
        this.multiLevelCache = multiLevelCache;
    }

    public JetcacheTokenStorage(Cache multiLevelCache, int concurrency) {
        this.multiLevelCache = multiLevelCache;
        this.concurrency = concurrency;
    }

    @Override
    @SuppressWarnings("unchecked")
    public String getUserKey(String token) {
        Object o = multiLevelCache.get(keyBefore(JETCACHE_TOKEN, token));
        if (o instanceof String) {
            return (String) o;
        }
        return null;
    }

    @Override
    @SuppressWarnings("unchecked")
    public Authentication getAuthentication(String userKey) {
        Object o = multiLevelCache.get(keyBefore(JETCACHE_AUTHENTICATION, userKey));
        if (o instanceof Authentication) {
            return (Authentication) o;
        }
        return null;
    }

    @Override
    @SuppressWarnings("unchecked")
    public void updateAuthentication(String userKey, Authentication authentication) {
        if (multiLevelCache.get(keyBefore(JETCACHE_AUTHENTICATION, userKey)) == null) {
            return;
        }
        multiLevelCache.put(keyBefore(JETCACHE_AUTHENTICATION, userKey), authentication);
    }

    @Override
    @SuppressWarnings("unchecked")
    public void setAuthentication(String client, String type, String userKey, Authentication authentication, SecurityToken token) {
        multiLevelCache.put(keyBefore(JETCACHE_TOKEN, token.getToken()), userKey, token.getTokenExpire(), token.getTimeUnit());
        try {
            multiLevelCache.put(keyBefore(JETCACHE_REFRESH_TOKEN, token.getRefreshToken()), new RefreshStorage().setClient(client).setType(type).setUserId(SecurityTool.getUserId(type, userKey)), token.getRefreshTokenExpire(), token.getTimeUnit());
            multiLevelCache.put(keyBefore(JETCACHE_AUTHENTICATION, userKey), authentication, token.getTokenExpire(), token.getTimeUnit());
            Object o = multiLevelCache.get(keyBefore(JETCACHE_USER_LIST, userKey));
            if (o == null) {
                o = new ArrayList<>(concurrency());
            }
            if (o instanceof List) {
                ((List) o).add(token);
            }
            multiLevelCache.put(keyBefore(JETCACHE_USER_LIST, userKey), o, token.getTokenExpire(), token.getTimeUnit());
        } catch (Exception e) {
            //这里如果发生异常，只回滚JETCACHE_TOKEN即可，至于刷新token和Authentication过期自动就没了，效验也不会效验这俩个
            multiLevelCache.remove(keyBefore(JETCACHE_TOKEN, token.getToken()));
            throw new RuntimeException(e.getMessage(), e);
        }

    }

    @Override
    @SuppressWarnings("unchecked")
    public List<SecurityToken> getToken(String userKey) {
        Object o = multiLevelCache.get(keyBefore(JETCACHE_USER_LIST, userKey));
        if (o instanceof List) {
            return (List<SecurityToken>) o;
        }
        return null;
    }

    @Override
    @SuppressWarnings("unchecked")
    public RefreshStorage getRefreshTokenStorage(String refreshtoken) {
        Object o = multiLevelCache.get(keyBefore(JETCACHE_REFRESH_TOKEN, refreshtoken));
        if (o instanceof RefreshStorage) {
            return (RefreshStorage) o;
        }
        return null;
    }

    @Override
    @SuppressWarnings("unchecked")
    public void kick(String token, String cause) {
        //剔出信息，过期12个小时
        Object userKey = multiLevelCache.get(keyBefore(JETCACHE_TOKEN, token));
        if (userKey != null) {
            //查询当前所有在线用户
            List<SecurityToken> userList = getToken(userKey.toString());
            if (userList != null && userList.size() > 1) {
                //移除当前在线人 JETCACHE_TOKEN,JETCACHE_REFRESH_TOKEN
                //更新JETCACHE_USER_LIST
                multiLevelCache.removeAll(CollUtil.newHashSet(keyBefore(JETCACHE_TOKEN, token),
                        keyBefore(JETCACHE_REFRESH_TOKEN, userList.get(0).getRefreshToken())));
                multiLevelCache.tryLockAndRun(keyBefore(JETCACHE_USER_LIST, userKey.toString()), 100, TimeUnit.SECONDS, () -> {
                    userList.removeIf(next -> next.getToken().equals(token));
                    multiLevelCache.put(keyBefore(JETCACHE_USER_LIST, userKey.toString()), userList);
                });
            } else {
                //移除当前在线人 JETCACHE_TOKEN,JETCACHE_AUTHENTICATION,JETCACHE_REFRESH_TOKEN，JETCACHE_USER_LIST
                multiLevelCache.removeAll(CollUtil.newHashSet(keyBefore(JETCACHE_TOKEN, token), keyBefore(JETCACHE_AUTHENTICATION, userKey.toString()),
                        keyBefore(JETCACHE_REFRESH_TOKEN, userList.get(0).getRefreshToken()), keyBefore(JETCACHE_USER_LIST, userKey.toString())));
            }
            multiLevelCache.put(keyBefore(JETCACHE_KIKT, token), cause, 12, TimeUnit.HOURS);
        }
    }

    @Override
    @SuppressWarnings("unchecked")
    public void kickUserKey(String userKey, String cause) {
        //查询当前所有在线用户
        List<SecurityToken> userList = getToken(userKey);
        if (userList == null) {
            return;
        }
        Map<String, String> kiktMap = new HashMap<>(userList.size());
        Set<String> removeKeySet = new HashSet<>();
        userList.forEach(token -> {
            kiktMap.put(keyBefore(JETCACHE_KIKT, token.getToken()), cause);
            removeKeySet.add(keyBefore(JETCACHE_TOKEN, token.getToken()));
            removeKeySet.add(keyBefore(JETCACHE_REFRESH_TOKEN, token.getRefreshToken()));
        });
        removeKeySet.add(keyBefore(JETCACHE_USER_LIST, userKey));
        removeKeySet.add(keyBefore(JETCACHE_AUTHENTICATION, userKey));
        //剔出所有信息
        multiLevelCache.removeAll(removeKeySet);
        multiLevelCache.putAll(kiktMap, 12, TimeUnit.HOURS);
    }

    @Override
    @SuppressWarnings("unchecked")
    public String getkickCause(String token) {
        Object o = multiLevelCache.get(keyBefore(JETCACHE_KIKT, token));
        if (o != null) {
            return o.toString();
        }
        return null;
    }

    @Override
    public void setAuthenticationConcurrency(String client, String type, String userKey, Authentication authentication, SecurityToken token) {
        List<SecurityToken> userList = getToken(userKey);
        if (userList != null && userList.size() >= concurrency()) {
            long end = (userList.size() - concurrency()) + 1;
            for (int i = 0; i < end; i++) {
                kick(userList.get(i).getToken(), "您的账号在别处登录，如不是本人操作，请及时修改密码");
            }
        }
        setAuthentication(client, type, userKey, authentication, token);
    }

    @Override
    public int concurrency() {
        return concurrency;
    }

    private String keyBefore(String before, String v) {
        return before + "_" + v;
    }
}
